package org.jpropelleralt.jview.table.impl;

import java.util.Map;

import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;

import org.jpropelleralt.box.Box;
import org.jpropelleralt.change.Change;
import org.jpropelleralt.ledger.Ledger;
import org.jpropelleralt.ledger.LedgerChange;
import org.jpropelleralt.ref.Ref;
import org.jpropelleralt.view.View;
import org.jpropelleralt.view.Views;
import org.jpropelleralt.view.update.Updatable;
import org.jpropelleralt.view.update.UpdateManager;

/**
 * A {@link TableModel} displaying a {@link Ledger}
 */
public class LedgerTableModel extends AbstractTableModel implements Updatable, View {

	private final Ref<? extends Ledger> value;
	private final UpdateManager updateManager;
	
	//Track whether we have had a complete change and/or column change since last firing
	private boolean rowCountChange = false;
	private boolean columnChange = false;
	
	//FIXME completely remove commented isFiring code.
//	private final AtomicBoolean isFiring = new AtomicBoolean(false);

	/**
	 * Create a {@link LedgerTableModel}
	 * @param value				The {@link Ledger}, as a {@link Ref}
	 * @param updateManager		The {@link UpdateManager} to use
	 */
	private LedgerTableModel(Ref<? extends Ledger> value, UpdateManager updateManager) {
		this.value = value;
		this.updateManager = updateManager;
		
		updateManager.registerUpdatable(this);

		value.features().addView(this);
		
		update();
	}
	
	/**
	 * Create a {@link LedgerTableModel}
	 * @param value				The {@link Ledger}, as a {@link Ref}
	 * @param updateManager		The {@link UpdateManager} to use
	 * @return					A new {@link LedgerTableModel}
	 */
	public static LedgerTableModel create(Ref<? extends Ledger> value, UpdateManager updateManager) {
		return new LedgerTableModel(value, updateManager);
	}

	/**
	 * Create a {@link LedgerTableModel}
	 * @param value				The {@link Ledger}, as a {@link Ref}
	 * @return					A new {@link LedgerTableModel}
	 */
	public static LedgerTableModel create(Ref<? extends Ledger> value) {
		return new LedgerTableModel(value, Views.updateManager());
	}

	@Override
	public void changes(Map<Box, Change> changes) {
		//If the prop has had a new instance set then we have a complete change
		if (changes.get(value).shallow()) {
			handleChange(true, true);
			
		//If the prop still points to the same list instance, see what change it has had
		} else {
			Ledger ledger = value.get();

			//If we have a null ledger, complete change
			if (ledger == null) {
				handleChange(true, true);				
			}
			
			//Find change for ledger
			Change tempLedgerChange = changes.get(ledger);
			
			//Work out the size of the change from the LedgerChange
			if (tempLedgerChange instanceof LedgerChange) {
				LedgerChange ledgerChange = (LedgerChange) tempLedgerChange;
				handleChange(ledgerChange.rowCountChanged(), ledgerChange.columnsChanged());
				
			//If we don't have a proper LedgerChange, complete change
			} else {
				handleChange(true, true);
			}
		}
	}

	private void handleChange(boolean newRowCountChange, boolean newColumnChange) {
		if (newRowCountChange) {
			rowCountChange = true;
		}
		if (newColumnChange) {
			columnChange = true;
		}

		//Ask for an update
		updateManager.updateRequiredBy(this);
	}

	public void update() {
		
		//TODO We use an AtomicBoolean here - probably not necessary since the
		//value will only ever be checked from the swing thread
//		isFiring.set(true);
		
		//Fire appropriate change
		if (columnChange) {
			fireTableStructureChanged();
		} else if (rowCountChange) {
			fireTableDataChanged();			
		} else {
			fireTableRowsUpdated(0, getRowCount()-1);
		}
		
//		isFiring.set(false);
		
		//We now haven't had a complete change since the last firing
		rowCountChange = false;
		columnChange = false;
	}
	
//	/**
//	 * True if this model is currently firing a change
//	 * @return	Is firing
//	 */
//	public boolean isFiring() {
//		return isFiring.get();
//	}
	
	//Pass through to Ledger
	
	@Override
	public Class<?> getColumnClass(int columnIndex) {
		Ledger ledger = value.get();
		if (ledger == null) {
			return null;
		} else {
			return ledger.fieldClass(columnIndex);
		}
	}

	@Override
	public int getColumnCount() {
		Ledger ledger = value.get();
		if (ledger == null) {
			return 0;
		} else {
			return ledger.fieldCount();
		}
	}

	@Override
	public String getColumnName(int columnIndex) {
		Ledger ledger = value.get();
		if (ledger == null) {
			return null;
		} else {
			return ledger.fieldName(columnIndex);
		}
	}

	@Override
	public int getRowCount() {
		Ledger ledger = value.get();
		if (ledger == null) {
			return 0;
		} else {
			return ledger.recordCount();
		}
	}

	@Override
	public Object getValueAt(int rowIndex, int columnIndex) {
		Ledger ledger = value.get();
		if (ledger == null) {
			return null;
		} else {
			return ledger.get(rowIndex, columnIndex);
		}
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		Ledger ledger = value.get();
		if (ledger == null) {
			return false;
		} else {
			return ledger.editable(rowIndex, columnIndex);
		}
	}
	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		Ledger ledger = value.get();
		if (ledger == null) {
			return;
		} else {
			ledger.set(rowIndex, columnIndex, aValue);
		}
	}
}
